`timescale 1ns / 1ps
//Name: Robert Smith
//PID: A08609119
//Name: Shreenidhi Chowkwale
//PID: A09089080
//
// CSE141L Lab 2, Part 1: Fetch Datapath
// University of California, San Diego
//
// Written by Donghwan Jeon, 4/10/2007
// Updated by Sat Garcia, 4/9/2008
// Updated by Michael Taylor, 4/4/2011

// Fetch unit module
//
// parameters:
//      I_WIDTH: instruction width
//      A_WIDTH: SRAM address width
//      O_WIDTH: offset width in a branch instruction (2's complement)
//

`include "instr_packet_s.v"

module fetch#(parameter I_WIDTH = 17, A_WIDTH = 10, O_WIDTH = 5, D_WIDTH = 34, FIFO_WIDTH = 68)
(
    input   clk,

    // inputs from exec unit
    input   dequeue_i,
    input   restart_i,
    input   [A_WIDTH-1: 0] restart_addr_i,
    input   [A_WIDTH-1: 0] jump_register_i,
    
    // memory interface
    input   load_store_valid_i,
    input   store_en_i,
    input   [A_WIDTH-1: 0] load_store_addr_i,
    input   [I_WIDTH-1: 0] store_data_i,
    output  [FIFO_WIDTH-1: 0] load_data_o,
    output  load_data_valid_o,

    // ouputs to the exec unit
    output  [FIFO_WIDTH-1: 0] instruction_data_o,
    output  [A_WIDTH-1: 0] instruction_addr_o,
    output  instruction_valid_o
);

	reg restart_r, load_store_valid_r, load_store_valid_r_2, store_en_r;
	reg load_data_valid_r;
	reg [3:0] sel_mux;
	
   wire fifo_enqueue, fifo_clear, fifo_valid, fifo_full, fifo_empty;
	wire load_data_valid_next;
   reg [I_WIDTH-1 : 0] offset;
   wire [A_WIDTH-1 : 0] ram_addr;
   wire [FIFO_WIDTH-1 : 0] ram_data;
	wire [FIFO_WIDTH + A_WIDTH - 1: 0] ip_in, ip_out;
   wire ram_we;

   reg is_staj;
   wire [A_WIDTH-1 : 0] pc_next;
	wire [A_WIDTH-1 : 0] plus_one, pc_plus_one, pc_plus_offset, pc_mux0, pc_mux1, pc_mux4;
	
    reg [A_WIDTH - 1 : 0] jump_register;
    
	assign plus_one = {{A_WIDTH-1{1'b0}}, 1'b1};

   // registers for the data path inputs
   reg [A_WIDTH-1 : 0]  load_store_addr_r;
   reg [I_WIDTH-1 : 0]  store_data_r;
   reg [A_WIDTH-1 : 0]  restart_addr_r;

   // validate inputs
   // synthesis translate off
   always_comb
     begin
        if (store_en_r & !load_store_valid_r)
          $display("Warning: store_en_r set but load_store_valid_r not set!\n");
     end
   // synthesis translate on

   reg  [A_WIDTH-1 : 0] pc_r;
	// Control Start
	always_ff @(posedge clk)
		begin
			restart_r <= restart_i;
			load_store_valid_r <= load_store_valid_i;
			load_store_valid_r_2 <= load_store_valid_r;
			store_en_r <= store_en_i;
		end

	wire [4:0] op_code_ram;
	assign op_code_ram = ram_data[67:63];

	//Troubles with X caused if statements
	always_comb
		begin
			// Checking instructions that do not require a jump.
			// swtch instruction does not require a jump.
			if(~((op_code_ram == 5'b00000)
				|| (op_code_ram == 5'b00010)
				|| (op_code_ram == 5'b00101)
				|| (op_code_ram == 5'b01000)
				|| (op_code_ram == 5'b01001)
				|| (op_code_ram == 5'b01011)
				|| (op_code_ram == 5'b01100)
				|| (op_code_ram == 5'b10000)
				|| (op_code_ram == 5'b10001)
				|| (op_code_ram == 5'b10111)
				|| (op_code_ram == 5'b11110)
				|| (op_code_ram == 5'b11111)))
				sel_mux[0] = 1'b0;
			else
				sel_mux[0] = 1'b1;
			if(fifo_enqueue)
				sel_mux[1] = 1'b1;
			else
				sel_mux[1] = 1'b0;
			if(restart_r)
				sel_mux[2] = 1'b0;
			else
				sel_mux[2] = 1'b1;
			if(load_store_valid_r)
				sel_mux[3] = 1'b1;
			else
				sel_mux[3] = 1'b0;
            if(op_code_ram == 5'b01010)
                is_staj = 1'b1;
            else
                is_staj = 1'b0;
		end

	assign ram_we = load_store_valid_r & store_en_r;
	assign fifo_enqueue = ~load_store_valid_r_2 & ~fifo_full & ~restart_r;
	assign fifo_clear = restart_r;
	//Not completely sure on this
	assign load_data_valid_next = load_store_valid_r & !store_en_r;

    always_ff @(posedge clk)
        begin
            if(op_code_ram == 5'b01001)
                jump_register <= pc_next;
        end
            
	always_ff @(posedge clk)
		begin
			load_data_valid_r <= load_data_valid_next;
		end

	assign load_data_valid_o = load_data_valid_r;
	//The instruction will be invalid if fifo is empty since there are no instructions.
	assign instruction_valid_o = ~restart_r & ~fifo_empty;
	// Control end
	
   // fifo instantiation
   fifo fetch_fifo
     (
      .clk(clk)
      ,.instr_packet_i(ip_in)
      ,.deque_i(dequeue_i)
      ,.clear_i(fifo_clear)
      ,.enque_i(fifo_enqueue)
      ,.instr_packet_o(ip_out)
      ,.empty_o(fifo_empty)
      ,.full_o(fifo_full)
      ,.valid_o(fifo_valid)
      );

	reg [A_WIDTH - 1 : 0] ram_addr_label, ram_addr_upper_imm, ram_addr_lower_imm;
	reg [I_WIDTH - 1 : 0] ram_data_instr, ram_data_label, ram_data_upper_imm, ram_data_lower_imm;
	
	always_comb
		begin
			if(ram_we)
				begin
					ram_addr_label = ram_addr;
					ram_addr_upper_imm = ram_addr;
					ram_addr_lower_imm = ram_addr;
				end
			else
				begin
					ram_addr_label = ram_addr + 1;
					ram_addr_upper_imm = ram_addr + 2;
					ram_addr_lower_imm = ram_addr + 3;
				end
		end

   // RAM instantiation
       imem_17_1024_1port ram_instruction
     (
      .address(ram_addr)
      ,.clock(clk)
      ,.data(store_data_r)
      ,.wren(ram_we)
      ,.q(ram_data_instr)             //data_out
      );
		
	imem_17_1024_1port ram_label
     (
      .address(ram_addr_label)
      ,.clock(clk)
      ,.data(store_data_r)
      ,.wren(ram_we)
      ,.q(ram_data_label)             //data_out
      );
		
	imem_17_1024_1port ram_upper_immediate
     (
      .address(ram_addr_upper_imm)
      ,.clock(clk)
      ,.data(store_data_r)
      ,.wren(ram_we)
      ,.q(ram_data_upper_imm)             //data_out
      );
	
	imem_17_1024_1port ram_lower_immediate
     (
      .address(ram_addr_lower_imm)
      ,.clock(clk)
      ,.data(store_data_r)
      ,.wren(ram_we)
      ,.q(ram_data_lower_imm)             //data_out
      );
		
	assign ram_data = {ram_data_instr, ram_data_label, ram_data_upper_imm, ram_data_lower_imm};

	assign ip_in = {ram_data, pc_r};
	assign load_data_o = ram_data;
	assign {instruction_data_o, instruction_addr_o } = ip_out;
	
	always_comb
		begin
			// Offset = 4 or a label
			if((op_code_ram == 5'b00001)
				|| (op_code_ram == 5'b00011)
				|| (op_code_ram == 5'b00100)
				|| (op_code_ram == 5'b01101)
				|| (op_code_ram == 5'b10010))
				offset = pc_r + 3'b100;
			else
				offset = ram_data_label;
		end
	
	always_ff @(posedge clk)
		begin
			restart_addr_r <= restart_addr_i;
			load_store_addr_r <= load_store_addr_i;
			store_data_r <= store_data_i;
		end
		
	adder adder_1
		(
			.d0_i(pc_r)
			,.d1_i(plus_one)
			,.d_o(pc_plus_one)
		);
		
	mux mux0
		(
			.sel_i(sel_mux[0])
			,.d0_i(offset)
			,.d1_i(pc_plus_one)
			,.d_o(pc_mux0)
		);
		
   mux mux4
        (
            .sel_i(is_staj)
            ,.d0_i(pc_mux0)
            ,.d1_i(jump_register)
            ,.d_o(pc_mux4)
        );
		
	mux mux1
		(
			.sel_i(sel_mux[1])
			,.d0_i(pc_r)
			,.d1_i(pc_mux4)
			,.d_o(pc_mux1)
		);
	

        
	mux mux2
		(
			.sel_i(sel_mux[2])
			,.d0_i(restart_addr_r)
			,.d1_i(pc_mux1)
			,.d_o(pc_next)
		);
		
	mux mux3
		(
			.sel_i(sel_mux[3])
			,.d0_i(pc_next)
			,.d1_i(load_store_addr_r)
			,.d_o(ram_addr)
		);
		
	always_ff @(posedge clk)
		begin
			pc_r <= pc_next;
		end
   
endmodule
